﻿using System;
using System.Diagnostics;
using ALDIOrganizer;

namespace Generator
{
    // this class contains partial solution of the problem
    [DebuggerDisplay("penalty = {penalty}")]
    public struct Solution : IComparable<Solution>
    {
        public bool Complete;

        /* the solution is Flawed if an imperative criterium has not been met
         * (eg. store assistants are not supposed to work more than 40 hours, but some do.)
         * a Flawed solution is always considered "smaller" than one which is not
         * (whatever their penalties), which ensures that "unflawed" solutions are favoured */
        public bool Flawed;

        public Shift[] Shifts;
        public Candidate[] Candidates;
        public int OrphanedShiftsLeft; // keeps track On how many Shifts still have no candidate

        #region shift_emp table
        /* id1 dimension: emps_from_database (by id), id2 dimension: Shifts (by id).
         * 
         * is _employee #3 considered as a candidate for Shift #7?
         * look up the Value of shift_emp[7,3] - simple. */
        public bool[,] shift_emp;

        // easy accessor
        public bool this[int shiftID, int employeeID]
        {
            get
            {
                return shift_emp[shiftID, employeeID];
            }
            set
            {
                shift_emp[shiftID, employeeID] = value;
            }
        }

        #endregion

        private Parameters _parameters;
        public Parameters parameters
        {
            get { return _parameters; }
        }


        #region keyflow
        /* list of people who Open or Close
         * openers[0, 2] = does _employee #2 (id number) Open On Monday (Day #0)?
         * openers[3, 1] = does _employee #1 (id) Open On Thursday (Day #3)? etc. */
        public bool[,] openers;
        public bool[,] closers;

        /// <summary>
        /// is there a "keyflow" Connection between Day [_howManyWeHave] and [_howManyWeHave-1]?
        /// </summary>
        /// <remarks>
        /// <para>This array remembers whether *some* person, who opens On Day _howManyWeHave (e.g. _howManyWeHave = 2; wednesday), closes the Day before, so that there is "keyflow" for that Day.</para>
        /// </remarks>
        public bool[] keyflowchain;
        /// <summary>
        /// current number Value of keyflow (or number of bits "lit up" in the keyflowchain array
        /// </summary>
        public byte keyflowValue;

        /* somebody is closing the store the Sunday THE PREVIOUS WEEK - so it would be good
         * to optimize our Rota so that one of those people is opening On Monday.
         * On the other hand, we may not have this information. the previous week Rota may not have been
         * prepared, for example.
         * In such case, set considerPreviousWeek to false, and the engine will only worry about who'pShift opening
         * On Tuesday-Sunday. */
        public bool considerPreviousWeek;

        /* if we have somehow obtained the data from the previous week, then closers[6] tells us
         * who was closing LAST SUNDAY.
         * closers[6, 0] - was _employee #1 closing LAST SUNDAY? closers[6, 1] - etc...
         * 
         * otherwise - if considerPreviousWeek is false - closers[6] contains NOTHING and will be ignored,
         * because it doesn't matter to us who is closing THIS SUNDAY. it may matter When we are preparing
         * next Rota, but for now it doesn't make a difference for the key flow. */
        #endregion


        public int penalty; // current penalty

        // constructor
        public Solution(Parameters parameters)
            : this()
        {
            _parameters = parameters;
            #region openers / closers [initializing both Lists]
            // TODO: initialize openers & closers
            #endregion

        }

        public Solution DeepCopy()
        {
            var copy = new Solution(parameters)
                           {
                               Candidates = new Candidate[this.Candidates.Length]
                           };

            for (int i = 0; i < copy.Candidates.Length; i++)
            {
                copy.Candidates[i] = this.Candidates[i].DeepCopy();
            }

            copy.closers = Engine.SquareArrayDeepCopy<bool>(this.closers);
            copy.considerPreviousWeek = this.considerPreviousWeek;                        
            copy.Complete = this.Complete;
            copy.Flawed = this.Flawed;
            copy.keyflowchain = Engine.ArrayDeepCopy<bool>(this.keyflowchain);
            copy.keyflowValue = this.keyflowValue;
            copy.openers = Engine.SquareArrayDeepCopy<bool>(this.openers);
            copy.penalty = this.penalty;
            copy.shift_emp = Engine.SquareArrayDeepCopy<bool>(this.shift_emp);

            copy.Shifts = new Shift[this.Shifts.Length];
            if (copy.Shifts.Length > 0)
            {
                for (int i = 0; i < copy.Shifts.Length; i++)
                    copy.Shifts[i] = this.Shifts[i].DeepCopy();
            }

            copy.OrphanedShiftsLeft = this.OrphanedShiftsLeft;
            copy._parameters = this._parameters;
            
            return copy;
        }

        public void ComputePenalty(bool pIsReportRequested, out Report pReport)
        {
            if (pIsReportRequested)
            {
                pReport = new Report();
            }
            else
            {
                pReport = null;
            }

            double _penalty = 0;

            Flawed = false;

            // hour penalty
            if (pIsReportRequested)
            {
                pReport.AllFlaws.AssistantsHourLimit =
                    Penalties.Parameters.StoreAssistantsMaximumHours;
                pReport.AllFlaws.StoreRunnersHourLimit =
                    Penalties.Parameters.StoreRunnersMaximumHours;
            }
            foreach (var ec in Candidates)
            {
                var _hour = Penalties.penaltyHours(ec.Hours, ec.ContractHrs);
                var _day = Penalties.penaltyDays(ec.Days, Penalties.preferredNumberOfWorkingDays(ec.ContractHrs));
                var _revisit = Penalties.revisitPenalty * ec.Revisits.NumberOfDays();
                
                
                var _fullShifts = Penalties.penaltyFullShiftsInARow(ec.FullDays);
                var _weekends = Penalties.penaltyWeekend(ec.Days, 7);
                _weekends += Penalties.penaltyWeekend(ec.FullDays, 7);
                
                _penalty += _hour;
                _penalty += _day;
                
                _penalty += _fullShifts;
                _penalty += _weekends;
                _penalty += _revisit;

                if (ec.IsOnVisa &&
                    ec.Hours > (ec.ContractHrs * 4))
                {
                    Flawed = true;
                }

                if (Penalties.Parameters.StoreAssistantsMaximumHours > 0)
                {
                    if (!ec.IsStoreRunner)
                    {
                        if (ec.Hours > (Penalties.Parameters.StoreAssistantsMaximumHours * 4))
                        {
                            Flawed = true;
                            if (pIsReportRequested)
                            {
                                pReport.AllFlaws.AddStoreAssistant(ec.ID,
                                    ec.Hours / 4F);
                            }
                        }
                    }
                }
                if (Penalties.Parameters.StoreRunnersMaximumHours > 0)
                    if (ec.IsStoreRunner)
                        if (ec.Hours > (Penalties.Parameters.StoreRunnersMaximumHours * 4))
                        {
                            Flawed = true;
                            if (pIsReportRequested)
                            {
                                pReport.AllFlaws.AddStoreRunner(ec.ID,
                                    ec.Hours / 4F);
                            }
                        }                
            }

            if (Penalties.Parameters.KeyFlowOptimizationOn)
            {
                var _keyflow = new KeyFlow();
                keyflowValue = (byte)Penalties.ComputeKeyFlow(openers,
                    closers,
                    Candidates,
                    parameters.ConsiderPreviousWeek,
                    pIsReportRequested,
                    out _keyflow);
                if (pIsReportRequested)
                {                    
                    pReport.KeyCycle = _keyflow;
                    pReport.KeyCycle.On = true;
                }
            }

            penalty = Convert.ToInt16(_penalty);
            
            if (Penalties.Parameters.KeyFlowOptimizationOn)            
            {
                penalty +=
                    Penalties.keyflowPenalties[(parameters.ConsiderPreviousWeek ? 7 : 6)
                                                                            - keyflowValue]
                                                                            * Candidates.Length; // !!!                                                    
            }
            if (!pIsReportRequested)
            {
                return;
            }
            pReport.Completed = DateTime.Now;
            if (Flawed)
            {
                pReport.Outcome = RotaGenerationOutcome.PartlySuccessful;
            }
        }

        #region IComparable<Solution> Members

        public int CompareTo(Solution other)
        {
            if (this.Flawed)
                if (!other.Flawed)
                    return 1;
            if (other.Flawed)
                if (!this.Flawed)
                    return -1;


            return this.penalty.CompareTo(other.penalty);
        }

        #endregion
    }

}
